package edu.drexel.cs.eVoter;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.log4j.Logger;
import org.jgroups.JChannel;
import org.jgroups.ReceiverAdapter;
import org.jgroups.View;
import org.jgroups.blocks.ReplicatedHashMap;
import org.jgroups.blocks.atomic.Counter;
import org.jgroups.blocks.atomic.CounterService;

public class Election implements Serializable {
	private static final long serialVersionUID = 5039314775980528587L;
	private static Logger logger = Logger.getLogger(Election.class);

	private Date date = null;
	private Country country = null;

	// State objects.
	private CounterService nationalCounterService = null;
	private ReplicatedHashMap<String, Counter> nationalVoteCounterMap = null;
	private ReplicatedHashMap<String, Voter> registeredVotersMap = null;
	private ReplicatedHashMap<String, Candidate> candidatesMap = null;
	private ReplicatedHashMap<String, CounterService> stateCounterServiceMap = null;
	private ReplicatedHashMap<String, HashMap<String, Counter>> stateVoteCounterMap = null;
	private ReplicatedHashMap<String, JChannel> stateChannelMap = null;
	private ReplicatedHashMap<String, Voter> votedMap = null;

	private static final String properties = "SHARED_LOOPBACK:PING(timeout=1000):"
			+ "pbcast.NAKACK(use_mcast_xmit=false;log_discard_msgs=false;log_not_found_msgs=false)"
			+ ":UNICAST:pbcast.STABLE(stability_delay=200):pbcast.GMS:FC:FRAG2:COUNTER";

	private JChannel channel = null;

	public Election(String countryName, String[] stateNames,
			String[] candidateNames, String[] voterIds,
			Map<String, String> voterToStateMap) throws Exception {
		date = new Date();
		this.country = new Country(countryName, getStates(stateNames));

		channel = new JChannel(properties);
		channel.setReceiver(new ReceiverAdapter() {
			public void viewAccepted(View view) {
				System.out.println("-- National View: " + view);
			}
		});
		channel.connect("VoteCounterGroup");

		nationalCounterService = new CounterService(channel);
		nationalVoteCounterMap = new ReplicatedHashMap<String, Counter>(channel);
		stateChannelMap = new ReplicatedHashMap<String, JChannel>(channel);
		registeredVotersMap = new ReplicatedHashMap<String, Voter>(channel);
		candidatesMap = new ReplicatedHashMap<String, Candidate>(channel);
		votedMap = new ReplicatedHashMap<String, Voter>(channel);
		
		initializeCandidates(candidateNames);
		initializeVoters(voterToStateMap);
	}

	private List<State> getStates(String[] stateNames) {
		List<State> states = new ArrayList<State>();
		for (String stateName : stateNames) {
			states.add(new State(stateName));
		}

		return states;
	}

	private void initializeCandidates(String[] candidateNames) {
		for (String candidateName : candidateNames) {
			candidatesMap.put(candidateName, new Candidate(candidateName));
			// Initialize all candidates' counters to 0.
			Counter counter = nationalCounterService.getOrCreateCounter(
					candidateName, 0);
			nationalVoteCounterMap.put(candidateName, counter);
		}
	}

	private void initializeVoters(Map<String, String> voterToStateMap) {
		for (Entry<String, String> entry : voterToStateMap.entrySet()) {
			String voterId = entry.getKey();
			String stateName = entry.getValue();
			State state = new State(stateName);
			Voter voter = new Voter(voterId, state);

			registeredVotersMap.put(voterId, voter);
		}
	}

	public Date getDate() {
		return date;
	}

	public void setDate(Date date) {
		this.date = date;
	}

	public Country getCountry() {
		return country;
	}

	public void setCountry(Country country) {
		this.country = country;
	}

	public List<State> getStates() {
		return country.getStates();
	}

	public List<Candidate> getCandidates() {
		List<Candidate> candidates = new ArrayList<Candidate>();
		candidates.addAll(candidatesMap.values());
		return candidates;
	}

	public boolean vote(String voterId, String candidateName) {
		boolean result = false;
		
		if(!votedMap.containsKey(voterId))
		{
			Counter nationalCounter = nationalVoteCounterMap.get(candidateName);
			long value1 = nationalCounter.incrementAndGet();
			logger.info("Candidate " + candidateName + " has " + value1
					+ " vote(s).");
	
			Voter voter = registeredVotersMap.get(voterId);
	
			HashMap<String, Counter> stateCounterMap = stateVoteCounterMap
					.get(voter.getState().getName());
	
			if(stateCounterMap == null)
			{
				registerStateChannel(voter.getState().getName());
				stateCounterMap = stateVoteCounterMap
						.get(voter.getState().getName());
			}
			
			Counter stateCounter = stateCounterMap.get(candidateName);
			long value2 = stateCounter.incrementAndGet();
			logger.info("Candidate " + candidateName + " has " + value2
					+ " vote(s) in " + voter.getState().getName());
			
			// Save voter in voted map.
			votedMap.put(voterId, voter);
			
			result = true;
		}
		
		return result;
	}

	public long result(String candidateName) {
		Counter counter = nationalVoteCounterMap.get(candidateName);
		return counter.get();
	}

	public String results(String stateName) {
		return stateCounterServiceMap.get(stateName).printCounters();
	}

	public String results() {
		return nationalCounterService.printCounters();
	}

	public State getVoterState(String voterId) {
		return registeredVotersMap.get(voterId).getState();
	}
	
	public boolean isStateChannelRegistered(String stateName)
	{
		return stateChannelMap.containsKey(stateName);
	}
	
	public void registerStateChannel(String stateName) {
		try {
			if (!isStateChannelRegistered(stateName)) {

				JChannel stateChannel = new JChannel(properties);
				stateChannel.setReceiver(new ReceiverAdapter() {
					public void viewAccepted(View view) {
						System.out.println("-- State View: " + view);
					}
				});
				stateChannel.connect(stateName + "VoteCounterGroup");

				CounterService stateCounterService = new CounterService(
						stateChannel);

				HashMap<String, Counter> stateCandidateMap = new HashMap<String, Counter>();

				for (Candidate candidate : getCandidates()) {
					// Initialize all candidates' counters to 0 for all state
					// tallies.
					Counter counter = stateCounterService.getOrCreateCounter(
							candidate.getName(), 0);
					stateCandidateMap.put(candidate.getName(), counter);
				}

				if (stateCounterServiceMap == null) {
					stateCounterServiceMap = new ReplicatedHashMap<String, CounterService>(
							stateChannel);
				}

				if (stateVoteCounterMap == null) {
					stateVoteCounterMap = new ReplicatedHashMap<String, HashMap<String, Counter>>(
							stateChannel);
				}

				stateCounterServiceMap.put(stateName, stateCounterService);
				stateVoteCounterMap.put(stateName, stateCandidateMap);
				stateChannelMap.put(stateName, stateChannel);
			}
		} catch (Exception e) {
			logger.error("Election.registerStateServer(); State: " + stateName
					+ "; " + e.getMessage(), e);
		}
	}
}
